from queue import Queue
from datetime import datetime
from pyqtgraph import PlotItem
import pyqtgraph as pg
from ExtendWidgets.notifycation import NotificationWindow
from model_test.test_thread_v2 import *
from . import *
from typing import *
import os
import numpy as np
import traceback


class TimeAxisItem(pg.AxisItem):
    def tickStrings(self, values, scale, spacing):
        return [datetime.fromtimestamp(value) for value in values]


class TestDockWidget(Ui_TestDockWidget, QDockWidget):

    def __init__(self, parent=None, threshold=None, freq_band=None, stftsetting: Dict[str, Union[int, float]] = None,
                 model_name=None):
        super(TestDockWidget, self).__init__(parent)
        self.setupUi(self)
        self.testing = False
        self.stftsetting = stftsetting
        self.model_name = model_name
        # self.test()
        self.maxrow = 100
        self.max_store_loss_length = 1600
        self.first_plot = True
        # self.tableWidget_anomaly.setRowCount(self.maxrow)
        self.currentRow = 0
        self.origin_plot.setPredefinedGradient('spectrum')
        self.reconstruction_plot.setPredefinedGradient('spectrum')
        self.threshold = threshold
        self.freq_band = freq_band
        self.pushButton_pause.clicked.connect(self.pause)
        self.pushButton_recover.clicked.connect(self.recover)
        self.dateTimeEdit_begin_time.setCalendarPopup(True)
        self.paused = False
        # todo:check
        from main import MainWin
        self.parent: MainWin = self.parent()
        self.lineEdit_test_data_path.setReadOnly(True)
        self.lineEdit_model_save_path.setReadOnly(True)
        self.lineEdit.setReadOnly(True)
        self.lineEdit_model_save_path.cursorPositionChanged.connect(
            lambda: self.lineEdit_model_save_path.setCursorPosition(0))
        self.lineEdit_test_data_path.cursorPositionChanged.connect(
            lambda: self.lineEdit_test_data_path.setCursorPosition(0))
        # 设置样式
        self.pushButton_get_model_save_path.setStyleSheet("min-width: 25px")
        self.pushButton_get_test_data_path.setStyleSheet("min-width: 25px")
        self.pushButton_save_current_result.hide()
        # todo
        self.ano_lst = []

        timeItem = TimeAxisItem(orientation='bottom')
        self.test_loss_plot_widget.setAxisItems(axisItems={"bottom": timeItem})
        self.test_loss_plotItem: PlotItem = self.test_loss_plot_widget.plotItem
        self.test_loss_plotItem.setLabel(axis='bottom', text="Time")
        self.test_loss_plotItem.setLabel(axis='left', text="Loss")
        self.test_loss_plotItem.setTitle('实时测试重构损失')
        self.loss_plot = self.test_loss_plotItem.plot(pen='g')
        pen = pg.mkPen('r', width=2)
        self.thresh_plot = self.test_loss_plotItem.plot(pen=pen)
        self.loss_lst = []
        self.time_lst = []
        self.lineEdit_current_state.setText("未检测")
        self.setQSSStyle()
        self.setImage()
        self.test()
        ################################# v2 #############################################
        # self.testResultQueue = Queue()
        # self.max_result_length = 10
        # self.timer = QTimer(self)  # 用于定时查看缓存队列是否满
        # self.timer.timeout.connect(self.checkResultQueue)
        #
        # self.showResultTimer =  QTimer(self)  # 用于展示结果
        # self.showResultTimer.timeout.connect(self.showTestResult)
    def setQSSStyle(self):
        self.dateTimeEdit_begin_time.setStyleSheet( """
            *::down-button{
                image: url(./qss/psblack/add_bottom.png);
                width: 10px;
                height: 10px;
                padding: 0px 5px 2px 0px;
            }
            """)
        self.pushButton_last_page.setText(" 上一页")
        self.pushButton_next_page.setText("下一页 ")
        self.pushButton_goto.setText("跳转至 ")
        self.pushButton_check.setText("查看 ")
        # self.pushButton_last_page.setFixedSize(25, 25)

        # self.pushButton_last_page.setFocusPolicy(Qt.NoFocus)
    def setImage(self):
        self.pushButton_last_page.setIcon(QIcon("./images/182 left arrow.png"))
        self.pushButton_last_page.setIconSize(QSize(20,20))


        self.pushButton_next_page.setIcon(QIcon("./images/181 right arrow.png"))
        self.pushButton_next_page.setIconSize(QSize(20, 20))
        self.pushButton_next_page.setLayoutDirection(Qt.RightToLeft)

        self.pushButton_goto.setIcon(QIcon("./images/跳转.png"))
        self.pushButton_goto.setIconSize(QSize(20, 20))
        self.pushButton_goto.setLayoutDirection(Qt.RightToLeft)


        self.pushButton_check.setIcon(QIcon("./images/查看.png"))
        self.pushButton_check.setIconSize(QSize(20, 20))
        self.pushButton_check.setLayoutDirection(Qt.RightToLeft)

    def initAnonamliesDataTable(self):
        tb = self.tableWidget_anoamlies
        header = ['时间(Time)', '']

    def test(self):
        self.lineEdit_test_data_path.setText(r"D:\异常检测项目最新版\DATA\DATA5yi.bin")

    def showTestResult(self):
        """
           ret_data = {
                    'threshold':self.thresh,
                    'loss_lst':[],    # 列表
                    'abnormal_index':[],
                    'abnormal_data':{},
                    'time_lst': np.arange(test_data.shape[0]) * 1 / test_data.shape[0] + current_time
                }
        :return:
        """
        if self.testResultQueue.qsize() == 0:
            return
        try:
            dic = self.testResultQueue.get()
            print("loss_lst   ", len(dic['loss_lst']))
        except:
            traceback.print_exc()
            return
        loss_lst = dic['loss_lst']
        time_lst = dic['time_lst']
        print(len(loss_lst), len(time_lst))

    def checkResultQueue(self):
        pass

    def pause(self):
        if not self.paused:
            self.label_ab.setStyleSheet("background-color:gray")
            self.test_thread.mutex.lock()
            self.paused = True
            self.lineEdit_current_state.setText("已暂停")
            self.showNotifications(['info', "已暂停测试"])

    def recover(self):
        if self.paused:
            self.test_thread.mutex.unlock()
            self.paused = False
            self.lineEdit_current_state.setText("正在检测")
            self.showNotifications(['info', "已恢复测试"])

    @pyqtSlot()
    def on_pushButton_get_test_data_path_clicked(self):
        # print("获取测试数据路径")
        # path = QFileDialog.getExistingDirectory(self, "测试数据路径选取", ".", QFileDialog.ShowDirsOnly)
        # if path == "":
        #     return
        path, _ = QFileDialog.getOpenFileName(self, "训练数据路径选取", ".", "Bin Files(*.bin)")
        if path == "":
            return
        self.lineEdit_test_data_path.setText(os.path.abspath(path))
        self.lineEdit_test_data_path.setCursorPosition(0)

    @pyqtSlot()
    def on_pushButton_get_model_save_path_clicked(self):
        print("获取模型保存路径")
        path = QFileDialog.getExistingDirectory(self, "模型保存路径选取", ".", QFileDialog.ShowDirsOnly)
        if path == "":
            return
        if len(os.listdir(path)) == 0:
            QMessageBox.warning(self, "文件夹内容为空", "文件夹内容为空, 请重新选择",
                                QMessageBox.Yes)
            return
        self.lineEdit_model_save_path.setText(os.path.abspath(path))
        self.lineEdit_model_save_path.setCursorPosition(0)

    def checkParameters(self, test_data_path, test_model_save_path):
        if test_data_path == "":
            QMessageBox.warning(self, "参数设置错误", "测试数据路径为空")
            return False
        self.test_data_path = test_data_path

        if test_model_save_path == "":
            QMessageBox.warning(self, "参数设置错误", "无效的模型路径")
            return False
        self.test_model_save_path = test_model_save_path

        # if save_interval <= 0:
        #     self.save_interval = 2
        # else:
        #     self.save_interval = save_interval
        return True

    def getParameters(self):
        test_data_path = self.lineEdit_test_data_path.text()
        test_model_save_path = self.lineEdit_model_save_path.text()
        return test_data_path, test_model_save_path

    def showImage(self, images: List[np.ndarray]) -> None:

        vmin = min(images[0].min(), images[1].min())
        vmax = max(images[0].max(), images[1].max())

        # 共用一个图例
        self.origin_plot.setImage(images[0].T, scale=[1, self.draw_ratio], levels=(vmin, vmax))
        self.reconstruction_plot.setImage(images[1].T, scale=[1, self.draw_ratio], levels=(vmin, vmax))

    def showNotifications(self, lst: list):
        if lst[0] == "info":
            NotificationWindow.info("提示", lst[1])
        elif lst[0] == "warning":
            NotificationWindow.warning("提示", lst[1])
        elif lst[0] == "error":
            NotificationWindow.error("提示", lst[1])
        elif lst[0] == "success":
            NotificationWindow.success("提示", lst[1])

    def showOutput(self, output: list) -> None:
        """
           #
           #     :param output: [loss, thresh, time]
           #     :return:
           #
        """
        if len(self.loss_lst) >= self.max_store_loss_length:
            self.loss_lst.pop(0)
            self.time_lst.pop(0)
        self.loss_lst.append(float(output[0]))
        self.time_lst.append(float(output[2]))
        ts = self.time_lst[0]

        self.test_loss_plot_widget.setXRange(ts, ts + 1600 * self.time_interval)

        # if np.max(self.time_lst) - np.min(self.time_lst) <=1:
        #
        # else:
        #     self.time_lst.clear()
        #     self.loss_lst.clear()

        self.loss_plot.setData(self.time_lst, self.loss_lst)
        self.thresh_plot.setData(np.arange(self.max_store_loss_length) * self.time_interval + ts,
                                 np.ones([self.max_store_loss_length]) * float(output[1]))

    def printToTextBrowser(self, text, color='white', font_size='4', font_family='Times New Roman'):
        content = f"""
           <font size={font_size} color={color} style= "font-family:{font_family}">{text}</font>
           """
        self.textBrowser_test_output.append(content)

    @pyqtSlot()
    def on_pushButton_test_clicked(self):
        if self.testing:
            QMessageBox.warning(self, "警告", "当前正在测试, 请测试完毕后再试")
            return
        ret = self.checkParameters(*self.getParameters())
        if ret == False:
            return
        print("开始测试")
        global_path = self.parent.setting_dock.path_widget.gloabal_path.text()
        if global_path == "":
            global_path = self.parent.setting_dock.path_widget.gloabal_path.placeholderText()
        csv_store_path = os.path.join(global_path, self.freq_band, self.model_name, 'csv test output')

        if not os.path.exists(csv_store_path):
            os.makedirs(csv_store_path)

        # 获取时间
        self.begin_time = self.dateTimeEdit_begin_time.dateTime().toTime_t()
        print("当前时间戳：", self.begin_time)

        # todo : 创建测试线程并绑定槽函数
        ##################################################################    v1  ##################################################
        tsamples = int(self.stftsetting['time_segment'])
        fsamples = int(self.stftsetting['nfft'])
        nfft = int(self.stftsetting['nfft'])
        noverlap = int(self.stftsetting['noverlap'])
        Fs = float(self.stftsetting['Fs'] * 1e6)
        self.fsamples = fsamples
        self.tsamples = tsamples
        self.draw_ratio = int(7 * (self.fsamples / self.tsamples) / 25.6)

        self.test_thread = TestThread(self, test_data_path=self.test_data_path,
                                      test_model_save_path=self.test_model_save_path,
                                      thresh=self.threshold,
                                      freq_band=self.freq_band,
                                      test_data_save_path=csv_store_path,
                                      tsamples=tsamples,
                                      fsamples=fsamples,
                                      begin_time=self.begin_time,
                                      nfft=nfft,
                                      noverlap=noverlap,
                                      time_segment=tsamples,
                                      Fs=Fs)
        self.test_thread.requestShowImage[list].connect(self.showImage)
        # self.test_thread.requestShowAnomaly[list].connect(self.showAnomaly)
        self.test_thread.requestOutput[list].connect(self.showOutput)
        self.test_thread.requestChagenIndicatorColor[str].connect(self.setIndicatorColor)
        self.test_thread.finished.connect(self.clearDocks)
        self.test_thread.requestShowPromption[list].connect(self.showNotifications)

        self.test_thread.start()





        # self.time_interval = self.bin_file_reader.time_interval

    def putTestResultToQueue(self, dic):
        """
        ret_data = {
                    'threshold':self.thresh,
                    'loss_lst':[],    # 列表
                    'abnormal_index':[],
                    'abnormal_data':{},
                    'time_lst': np.arange(test_data.shape[0]) * 1 / test_data.shape[0] + current_time
                }
        :param dic:
        :return:
        """
        self.testResultQueue.put(dic)
        if self.testResultQueue.qsize() >= self.max_result_length:
            try:
                self.test_thread.mutex.lock()
                self.paused = True
            except:
                print('暂停失败')
        else:
            try:
                self.test_thread.mutex.unlock()
                self.paused = False
            except:
                print('恢复失败')

    def clearDocks(self):
        try:
            address = self.parent.findChild(TestDockWidget, name=self.objectName())
            self.parent.needToDeleteDocks.remove(address)
            print("删除了{}窗口".format(self.windowTitle()))
            self.deleteLater()
        except Exception as e:
            print(e)

    def setIndicatorColor(self, type: str):
        """
        :param type: 正常, 异常, 未测试
        :return:
        """
        color = {"正常": "chartreuse", "异常": "red", "未检测": "gray"}
        self.label_ab.setStyleSheet(f"background-color:{color[type]}")

    def closeEvent(self, event: QCloseEvent) -> None:
        try:
            if self.testing:
                ret = QMessageBox.warning(self, "窗口关闭警告", "关闭窗口后当前窗口执行的程序将被关闭，确定要关闭窗口吗？",
                                          QMessageBox.Ok | QMessageBox.Cancel)
                # ret = QMessageBox.warning(self, "窗口关闭警告", "当前正在训练模型, 不能关闭窗口",
                #                           QMessageBox.Ok )
                # event.setAccepted(False)
                if ret == QMessageBox.Cancel:
                    event.setAccepted(False)
                    return
                else:
                    self.test_thread.requestInterruption()
                    self.bin_file_reader.terminate()  # 强行终止, 程序可能中断
                    address = self.parent.findChild(TestDockWidget, name=self.objectName())
                    self.parent.docks.remove(address)
                    self.parent.needToDeleteDocks.append(address)
            else:
                self.parent.docks.remove(self.parent.findChild(TestDockWidget, name=self.objectName()))
                self.deleteLater()
        except Exception as e:
            print(e)
